package com.k8.common.aop;

import com.k8.common.aop.internal.interceptor.ExposeInvocationInterceptor;
import com.k8.common.aop.internal.invocation.MethodInvocation;
import com.k8.common.aop.internal.invocation.ReflectiveMethodInvocation;
import com.k8.common.util.AopUtil;
import net.sf.cglib.proxy.*;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

public class CglibAopProxy implements AopProxy {

    private AdvisedSupport advisedSupport;

    public CglibAopProxy(AdvisedSupport advisedSupport) {
        this.advisedSupport = advisedSupport;
    }

    @Override
    public Object getProxy() {
        ClassLoader classLoader = advisedSupport.getClassLoader();
        return getProxy(classLoader);
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        return buildProxy(this.determineClassLoader(classLoader), false);
    }

    private ClassLoader determineClassLoader(ClassLoader classLoader) {
        if (classLoader == null) {
            return this.getClass().getClassLoader();
        } else {
            if (classLoader.getParent() == null) {
                ClassLoader aopClassLoader = this.getClass().getClassLoader();

                for (ClassLoader aopParent = aopClassLoader.getParent(); aopParent != null; aopParent = aopParent.getParent()) {
                    if (classLoader == aopParent) {
                        return aopClassLoader;
                    }
                }
            }

            return classLoader;
        }
    }

    @Override
    public Class<?> getProxyClass(ClassLoader classLoader) {
        return (Class<?>) buildProxy(classLoader, true);
    }

    private Object buildProxy(ClassLoader classLoader, boolean isClassOnly) {
        Enhancer enhancer = new Enhancer();
        enhancer.setClassLoader(classLoader);
        enhancer.setSuperclass(advisedSupport.getTargetClass());
        enhancer.setAttemptLoad(true);
        enhancer.setClassLoader(classLoader);
        CallbackFilter callbackFilter = new DefaultCallbackFilter(advisedSupport);
        enhancer.setCallbackFilter(callbackFilter);
        Callback[] callbacks = this.getCallbacks();
        Class<?>[] callbacksClasses = Arrays.stream(callbacks).map(callback -> {
            return (Class<?>) callback.getClass();
        }).toList().toArray(new Class<?>[0]);
        if (isClassOnly) {
            enhancer.setCallbackTypes(callbacksClasses);
            return enhancer.createClass();
        }
        enhancer.setCallbacks(callbacks);
        return enhancer.create();
    }

    private Callback[] getCallbacks() {
        Callback[] callbacks = new Callback[2];
        callbacks[0] = new DynamicAdvisedInterceptor(advisedSupport);
        callbacks[1] = new SerializableNoOp();
        return callbacks;
    }

    public static class DefaultCallbackFilter implements CallbackFilter {
        AdvisedSupport advisedSupport;

        public DefaultCallbackFilter(AdvisedSupport advisedSupport) {
            this.advisedSupport = advisedSupport;
        }

        @Override
        public int accept(Method method) {
            if (AopUtil.isNoOpMethod(method)) {
                return 1;
            } else if (advisedSupport.getCacheAdvices().get(method) == null) {
                return 1;
            }
            return 0;
        }
    }

    public static class SerializableNoOp implements NoOp, Serializable {

    }

    public static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
        AdvisedSupport advisedSupport;

        public DynamicAdvisedInterceptor(AdvisedSupport advisedSupport) {
            this.advisedSupport = advisedSupport;
        }

        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Method method1 = advisedSupport.getTargetClass().getMethod(method.getName(), method.getParameterTypes());
            List<com.k8.common.aop.internal.interceptor.MethodInterceptor> interceptors = advisedSupport.getChain(method1);
            if (interceptors == null || interceptors.isEmpty()) {
                //直接执行父类的该方法
                return method.invoke(advisedSupport.getTarget(), args);
            }
            MethodInvocation mi = new ReflectiveMethodInvocation(proxy, advisedSupport.getTarget(), method, args, advisedSupport.getTargetClass(), interceptors, methodProxy);
            ExposeInvocationInterceptor exposeInvocationInterceptor = new ExposeInvocationInterceptor();
            interceptors.add(0, exposeInvocationInterceptor);
            return mi.proceed();
        }
    }
}
